﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LGPeWJNM.BaseClasses;

namespace LGPeWJNM.AntColony
{
    public class AntColony : BaseClasses.IRoteirizacao
    {
        public double alfa { get; set; }
        public double beta { get; set; }
        public double evaporacao { get; set; }
        public uint geracoes { get; set; }

        public AntColony(double alfa, double beta, double evaporacao, uint geracoes)
        {
            this.alfa = alfa;
            this.beta = beta;
            this.evaporacao = evaporacao;
            this.geracoes = geracoes;
        }
        public IEnumerable<LinkedList<BaseClasses.ICliente>> Processar(IEnumerable<IEnumerable<BaseClasses.ICliente>> destinos, BaseClasses.Point deposito, int quantidadeVeiculos, float capacidadeVeiculo)
        {
            foreach (var cluster in destinos)
                yield return YieldingProcess(deposito, cluster);
        }

        private LinkedList<ICliente> YieldingProcess(BaseClasses.Point deposito, IEnumerable<ICliente> cluster)
        {
            var rnd = new Random();
            var feromonio = new TriangleMatrix<int, double>();
            LinkedList<ICliente> resultado = null;

            if (cluster.First() is Cliente)
                for (int geracao = 0; geracao < geracoes; geracao++)
                {
                    var nos = new List<Cliente>(cluster.Cast<Cliente>());
                    var depositoAsCliente = new Cliente() { Coordenada = deposito, Demanda = 0, Id = 1 };
                    nos.Add(depositoAsCliente);

                    var rotasFormigas = new List<LinkedList<ICliente>>();

                    //coloca cada formiga sobre um no distinto
                    foreach (var no in nos)
                    {
                        var l = new LinkedList<ICliente>();
                        l.AddFirst(no);
                        rotasFormigas.Add(l);
                    }

                    //inicializa a matriz feromônio
                    foreach (var i in nos)
                        foreach (var j in nos)
                            if (i != j)
                                feromonio[i.Id, j.Id] = 0.1;

                    //System.Threading.Tasks.Parallel.ForEach(rotasFormigas, formiga =>
                    //{
                    foreach (var formiga in rotasFormigas)
                    {
                        var aVisitar = (from n in nos
                                        where !formiga.Contains(n)
                                        select n).ToList();
                        while (aVisitar.Count() > 0)
                        {
                            var i = formiga.Last.Value as Cliente;

                            var numeradorP = from j in aVisitar
                                             select new { j, nP = Math.Pow(feromonio[i.Id, j.Id], alfa) * Math.Pow(1 / (i.Coordenada % j.Coordenada), beta) };
                            var P = from n in numeradorP
                                    select new { n.j, P = n.nP / numeradorP.Select(s => s.nP).Sum() };

                            var r = rnd.NextDouble();

                            var rotaASeguir = (from p in P
                                               where r < (from p1 in P
                                                          orderby p1.P descending
                                                          where p1.P >= p.P
                                                          select p1.P).Sum()
                                               select p.j).First();
                            formiga.AddLast(rotaASeguir);
                            aVisitar.Remove(rotaASeguir);
                        }
                        //transforma rota aberta em ciclo Hamiltoniano
                        formiga.AddLast(formiga.First.Value);
                    }
                    //});
                    var Lk = from r in rotasFormigas
                             select new { r, Lk = 100 / r.CustoTotal() };

                    //Atualiza feromônio
                    foreach (var i in nos)
                        foreach (var j in nos)
                            if (i.Id > j.Id)
                                feromonio[i.Id, j.Id] *= evaporacao;

                    foreach (var item in Lk)
                        foreach (var r in item.r)
                            feromonio[((Cliente)r).Id, ((Cliente)((item.r.Find(r).Next ?? item.r.First).Value)).Id] += item.Lk;

                    //Salva melhor resultado da geração atual
                    if (resultado == null || resultado.CustoTotal() > rotasFormigas.OrderBy(s => s.CustoTotal()).First().CustoTotal())
                    {
                        var r = rotasFormigas.OrderBy(s => s.CustoTotal()).First();
                        if (r.First() == r.Last() && r.First().Demanda == 0)
                            return r;

                        r.RemoveLast();

                        int posicaoTroca = r.ToList().IndexOf(depositoAsCliente);
                        int comprimentoTroca = r.Count - posicaoTroca;

                        var trechoFim = r.Skip(posicaoTroca).Take(comprimentoTroca).ToArray();
                        var trechoInicio = r.Take(posicaoTroca).ToArray();

                        r = new LinkedList<ICliente>(trechoFim.Concat(trechoInicio));
                        r.AddLast(depositoAsCliente);

                        resultado = r;
                    }
                }
            return resultado;
        }
    }
}
